SublimeText2のプラグインを作る時のまとめ


概要

プラグインを作るときに、自分が調べて学んだこととか

調べ方とかを纏めておく。



参考にしたもの

なにはともあれまずAPIと、

http://www.sublimetext.com/docs/2/api_reference.html


作り方

http://net.tutsplus.com/tutorials/python-tutorials/how-to-create-a-sublime-text-2-plugin/


あと今まで使ってたプラグインを、参考までに開けて見てた。全部Package Controlに入ってるはず。

感謝。


InsertDate

どっちかっていうとPythonの勉強になった


Package Control

UIの勉強に超なった


Git

Gitの勉強になった

Scalariform

コマンドライン実行の勉強になった


commandについて

SublimeText2でよく見る、実行プロトコルみたいなもの。

値の名前がついてるPythonプログラムを実行する。 

JSON内で定義されている事が多いが、.py内から他のcommandも呼べる。


例えば、


"command": "myCommand"


とか書いてあると、


myCommand って名前をつけられてる特定条件を満たした.pyプログラムが実行される。


特定条件

command : コマンド名 に対して、


・このプラグインフォルダ中に、コマンド名を含んだclass が定義されている.pyファイルがある

.pyファイルならファイル名は何でも良くて、class名がコマンド名を含んでいるのが条件。

command : something なら、 SomethingHappen とかになる。


注意点として、cammelケースとかがあると、混乱のもと。


command : somethingelse とあったら、


class SomethingElse だと ×。

class Somethingelse で ○。 一度ハマった。


・その class が、次のどれかのSublimeのクラスを継承している。

sublime_plugin.ApplicationCommand

sublime_plugin.WindowCommand

sublime_plugin.TextCommand


それぞれの違いはAPIみてくだしあ。

http://www.sublimetext.com/docs/api-reference#sublimeplugin.Plugin


当然 import も必要。


・run メソッド(特定引数あり)を持っている

引数は上記クラスによって異なる。


各条件を満たした.pyの表記はこんな感じ。

.pyファイル

import sublime

import sublime_plugin


class コマンド名を含んだクラス名 (sublime_plugin.TextCommand):

  def run (self, edit) :



プラグイン基礎構造

プラグイン名のフォルダ

+-syntax

+-Default.sublime-commands

+-Default.sublime-keymap

+-Main.sublime-menu

+-プラグイン名.sublime-settings

+-その他 .pyとか、README.mdとか



何を変えるとどこが変わるのか

プラグインの中のファイル一覧と、その内容についての記述が無かったのでメモ。



Default.sublime-commands

commandの基礎リスト。

⌘ + shift + pから出てくる、プラグインに含まれてるcommand一覧が書いてある。

commandの条件を満たせば表示される。


Default.sublime-keymap

キーバインド、ショートカットとcommand(ST2での標準的な実行形式)の連携を書くファイル。

こんな感じ。

[

{ "keys": ["super+alt+b"], "command": "gbuild" }

,{ "keys": ["super+alt+t"], "command": "gtest" }

]

内容がすでに用意されている物とかぶると、上書きしちゃったりされちゃったりする。

ところで現在インストールされてるキーマップ一覧をチートシートみたいに表示するプラグインが欲しい。。



Main.sublime-menu

Toolsなど、Sublime上部のバーで表示する要素に追加する項目を記述する所。

jsonでの階層構造、とても奇麗。//でコメントが使える。

[

    {

        "id": "tools",

        "children":

        [

            {

                "caption": "Gradle",

                "children":

                [

                     { "caption": "Log", "command": "test" }

                ]

            }

        ]

    }


    ,{

        "caption": "Preferences",

        "mnemonic": "n",

        "id": "preferences",

        "children":

        [

            {

                "caption": "Package Settings",

                "mnemonic": "P",

                "id": "package-settings",

                "children":

                [

                    {

                        "caption": "Gradle",

                        "children":

                        [

                            {

                                "command": "open_file",

                                "args": {"file": "${packages}/Gradle/Gradle.sublime-settings"},

                                "caption": "Settings – Default"

                            },

                            {

                                "command": "open_file",

                                "args": {"file": "${packages}/User/Gradle.sublime-settings"},

                                "caption": "Settings – User"

                            },

                            { "caption": "-" }

                        ]

                    }

                ]

            }

        ]

    }

]


とか書くと、①Tools の項目にGradle、さらにその要素として Log が表示される。



③Preferences > package Settings に表示される際の名称。アプリの設定(Preferences)から選択できる。

だいたいのプラグインは、ここから設定ファイル ④、⑤プラグイン名.sublime-settings を開けるようにしているみたい。


メニューの項目はネスト可能で、ネストした場合、プルダウン表示される。

スクリーンショット 2012-11-02 2.27.54.png

簡単に作れて素敵。



Side Bar.sublime-menu

サイドバーで、メニュー表示時に表示される内容が書き足せる。

jsonでの階層構造、とても奇麗。コメントが使える。


プラグイン名.sublime-settings

設定ファイル。

プラグインのデフォルトがプラグインフォルダにあり、

上書き用の同名のファイルがSublimeのUser フォルダ下にあるのがコモンセンスらしい。



プラグイン製作中のSublimeの挙動について

control +  ` でSublime内のPythonコマンドライン窓が出るんだけど、

プラグインのファイルを編集→保存、とかやると、

[Reloading plugin /Users/sassembla/なにやら] とか表示されるので、プラグインは都度更新、ビルド、再読み込みがされている。



.pyファイルについて

プラグインのフォルダ内に存在してれば読み込まれる。



Toolsからのプラグインのcommandの実行について

[

    {

        "id": "tools",

        "children":

        [

            {

                "caption": "Gradle",

                "children":

                [

                     { "caption": "Build", "command": "build" }

                ]

            }

        ]

    }



①、②で、ToolにGradleメニューが現れるようになるが、

③のtest commandがちゃんとToolsからハイライト表示されるかどうかは、


commandの条件を満たす必要がある。


条件を満たさない場合、commandは灰色で表示され、実行できない。


実際の挙動を作る

具体例はちょっと思いつかないので、仮の例を挙げる。

・とあるプロセスをSublimeText2から実行したい


たとえばTypeScriptのテスト(って何まだ無いけど)を実行したいとか、

JenkinsのトリガーをSublimeText2から引きたいとか


そんな感じの物事があったとして、

「無事にPlugin作りました、簡単です、実行できます!」


→いざ実行→UIがロックされる

と殺意を覚える。コンマ一秒でもUIをロックしちゃだめだ。。。 

→できるだけ非同期に行って、でも結果はUIに返そうぜ!


そんなときは次の機能のペアが役に立つ。


threading.Thread


sublime.set_timeout(callback,  delay)


Androidやってる人ならピンと来るかもしれない。

iOSやってる人には全くピンと来ないと思う。

JSerな自分はJSな理解で誤解した。


threading.Thread は

スレッドです。


PythonのAPIに入ってるので調べてくだしあ。

このへん

http://docs.python.org/2/library/threading.html

http://www.doughellmann.com/PyMOTW-ja/threading/



sublime.set_timeout は

特定動作を指定時間後に実行する。


SublimeTextのAPIに含まれている。

http://www.sublimetext.com/docs/2/api_reference.html

このAPIは、

特定の関数(callback)を、delayミリ秒後にMainThreadで実行する

っていう感じのものになる。

"It is safe to call setTimeout from multiple threads."


この一文から解れってのが、ちょっとキツい。


これらを使って何をすると捗るか:

スレッド立ち上げてなんらかの処理を実行、

UIをロックせず、

終わったらUIに通知

というのが、この2つの組み合わせで出来る。

とても捗るのでお薦め。



以下おまけ話

よくあるクライアントサイドアプリの話として、

UIをもつアプリケーションは、須くUIを扱うThreadを持つわけで、

このUIThreadへのアクセスができないと、GUIになんにも反映できない。

良いプラットフォームはこのへんの隠蔽が上手。

SublimeText2だとどうなっているのか。


例えばSublimeTextのAPIに含まれている次の関数

status_message(string)


なんかは、stringに入れた内容をSublimeText2の最下部のバーに表示できるんだけど、


UIの要素なので、

UIThread(っていうかMainThread)からしか呼べない。

適当にThreadを継承したクラスを作って、インスタンスつくって、

インスタンス内でstatus_messageメソッドを呼ぼうとすると、Assertionで殺される。

たとえばこんなソースを書いたとする

    #どっかでスレッド継承クラスをinstantiateして、start

    thread = SomeThread("hahaha!")

    thread.start()


#スレッド継承クラス

class SomeThread(threading.Thread):

  def __init__(self, message):

    self. message = message

    threading.Thread.__init__(self)


  def run(self):

    print "run start, message is ", self.message

    sublime.status_message(self.message)


こいつは、次のエラーを呼ぶ。

Exception in thread Thread-397:

Traceback (most recent call last):

  File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading.py", line 532, in __bootstrap_inner

  File "./testing.py", line 40, in run

    sublime.status_message(self.command)

RuntimeError: Must call on main thread, consider using sublime.set_timeout(function, timeout)

(MainThread外から呼んでんじゃねーよksg!! set_timeout使え!!)


で、言われた通り、sublime.set_timeout を使うと、問題なく該当メソッドが呼べる。


delayミリ秒後、timeout先で呼ばれるのがmainThreadになっている、という絡繰り。

関数をぶん投げて、MainThread で実行される。



Androidだと、実行したい処理を含んだThreadを作ってrunOnUiThreadメソッドにThread放り込んで云々、って感じになるところ。

iOSだと、そもそもそんな残念な苦労はしなくてよくて、UIViewのクラスメソッドや、セレクタ渡しや、NSNotificationが超強力になんとかしてくれる。

JSだとそもThreadって概念無いので、はい。


Sublimeは、っていうと、だいたい上記のメソッドで、優雅では無いけど使いやすい感じに仕上がっていると思う。

せめてrunOnMainThread と runOnMainThreadWithDelay とかに出来なかったのか、、とか思うが、


関数そのまま渡せるのですっごく助かる。



メモ


OS判定

デフォルトでOS判別をしてくれてる。

詳しくは @ikeike443 さんのScalariformとか読むと良いと思う。

ikeike443 / Sublime-Scalariform

https://github.com/ikeike443/Sublime-Scalariform


Terminalみたいに何かを実行したいんだけど。

コマンドラインを実行するには。


self.view.window().run_command('exec', {'cmd': ['sh', 'script.sh'], 'quiet': False})  

→viewが存在するメインスレッドからのみ実行できる。

実行中の内容がPromptっぽいWindowで表示できるんだけど、Mainをがっちり掴んでしまう。

スレッド違いには出来ない。UIが絡んでるからだね。


subprocess.call(self.CMD+self.view.file_name(), shell=True)

→別スレッドでも実行可能。

Python API

http://www.python.jp/doc/release/library/subprocess.html

なんだかんだで Popen が無双。

SublimeText2の現在のデフォルトがPython2.6系なので、

check_outputが使えないのが残念。



viewの再描画

self._force_refresh()

強制読み直し



Toolでのプラグインの実行表記に、ショートカットが勝手についた  \(^o^)/わーい

プラグイン作ってる途中になんか、勝手についちゃった。

本来は、 Default.sublime-keymapに書かなければつかない、、はず、、


勝手にショートカットついちゃった//// 流石俺/////

スクリーンショット 2012-11-03 22.22.01.png

→なぜなんだぜ

→build というcommand名を用意していたんだが、それが不味かった。


デフォルトで存在するcommnd名 build とモロかぶりした。



デフォルトで存在するcommnd名を自作プラグインが定義してしまうと、プラグインのもので上書きされる。


で、デフォルトのbuildには、super + b というキーが Default.sublime-keymapで セットしてあったので、

基礎定義部分だけプラグインがオーバーライドして、表記などは Default.sublime-keymap のものが採用されてた、という。


部分オーバーライド(?)こええ。



ショートカットについてのハマりどころ

super + alt + t を とあるcommandのショートカットに指定してたんだけど、

スクリーンショット 2012-11-05 14.16.27.png

commandPaletteからは表示されるのに、

スクリーンショット 2012-11-05 14.16.53.png

Toolsからは表示されなくて

スクリーンショット 2012-11-05 14.18.19.png

「、、、何でなんだぜ、、? 一方では出て、もう一方で出ない、、強制上書きが発動しているはず、、では、、?」


って思ってたんですけど、これ、上書き不可とかも有るみたいですねー。

super + alt + tは、こいつだった。

スクリーンショット 2012-11-05 14.21.03.png

試しにcommand + alt + n に変更したら、commandPaletteの方も、Toolsのほうも、

ちゃんと表示されました。


泣いてません。